/* ***** BEGIN LICENSE BLOCK *****
 *   Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is ThumbsDown.
 *
 * The Initial Developer of the Original Code is
 * Atte Kemppilä.
 * Portions created by the Initial Developer are Copyright (C) 2008
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 * 
 * ***** END LICENSE BLOCK ***** */


function ImagesPage() // extend WizardPage
{
    var depends = "selected linkUrls";
    WizardPage.call(this, depends);
}

ImagesPage.prototype =
{
    get autoGuessImage()      { return (this.$autoGuessImage.getAttribute("checked") == "true"); },
    set autoGuessImage(value) { this.$autoGuessImage.setAttribute("checked", (!! value)); },
    
    get filterImages()      { return (this.$filterImages.getAttribute("checked") == "true"); },
    set filterImages(value) { this.$filterImages.setAttribute("checked", (!! value)); },
    
    get selectedCount() { return count(this.entries, function(e) { return ("image" in e.target) && e.target.image; }); },
    
    get $imageList() 
    {
        // first panel (index 0) is the no-thumbnail-selected-yet -panel
        return ((this.$pageDeck.selectedIndex == 0) ? null : this.$pageDeck.selectedPanel);
    },
    
    get filter()
    {
        if (! this.filterImages)
            return null;
        
        if (this._filter)
            return this._filter;
        
        var minWidth = ThumbsDown.prefs.getIntPref("scriptwizard.minImageWidth");
        var minHeight = ThumbsDown.prefs.getIntPref("scriptwizard.minImageHeight");
        var mode = ThumbsDown.prefs.getIntPref("scriptwizard.minImageMode");
        var boundingBox = new Box(minWidth, minHeight);
        
        return this._filter = function(rlitem)
        {
            // TODO: it might be better rely image's built-in properties instead of state ("slow" because of event ordering issues, see: _populateImageListbox())
            var image = rlitem.firstChild;
            if ((! image) || (image.state != "loaded"))
                return false;
            var imageBox = new Box(image.naturalWidth, image.naturalHeight);
            return boundingBox.contains(imageBox, (mode == FILTER_MODE_EITHER));
        }
    },
    
    onPageShow: function()
    {
        if (this.firstRun)
        {
            this.$autoGuessImage = document.getElementById("broadcaster_autoGuessImage");
            this.$filterImages = document.getElementById("broadcaster_filterImages");
            this.$thumbnailList = document.getElementById("rlbox_thumbnails");
            this.$pageDeck = document.getElementById("deck_pages");
            this.$imageListMenuPopup = document.getElementById("popup_images");
            this.$showLarger = document.getElementById("mitem_showLargerImage");
            
            wizard.addPrefObserver(this, "extensions.thumbsdown.scriptwizard.", false);
        }
        
        if (this.dirty.page)
        {
            this.entries =
                scriptWizard.getEntries({
                    thumbnail: model.selected,
                    linkUrl: model.linkUrls,
                });
            
            // page loaders
            var self = this;
            var headers = {"Referer": model.referer};
            var timeout = ThumbsDown.prefs.getIntPref("scriptwizard.requestTimeout");
            
            for each (let _entry in this.entries)
            {
                let entry = _entry;
                let uri = Services.io.newURI(entry.linkUrl, null, null);
                let onLoad = function(loader) { self.onPageLoad(loader, entry); }
                let onError = function(loader) { self.onPageError(loader, entry); }
                
                entry.target = {};
                entry.target.loader = new ResourceLoader("GET", uri, headers, onLoad, onError, timeout);
                entry.target.loader.load();
            }
            
            // clear left hand side richlistbox
            while (this.$thumbnailList.itemCount > 0)
                this.$thumbnailList.removeItemAt(0);
            
            this.$pageDeck.selectedIndex = 0;
            
            // clear right hand side deck
            var rlbox = this.$pageDeck.firstChild;
            while (rlbox.nextSibling)
                this.$pageDeck.removeChild(rlbox.nextSibling);
            
            for each (let entry in this.entries)
            {
                let thumbnail = entry.thumbnail;
                
                // left hand side
                let attrs = {maxwidth: 128, maxheight: 128, src: thumbnail.thumbnailUrl, class: "show-throbber show-error"};
                var scaledimage = createElement(document, NS_XUL, "scaledimage", attrs);
                var rlitem = createElement(document, NS_XUL, "richlistitem", null, [scaledimage]);
                this.$thumbnailList.appendChild(rlitem);
                
                // right hand side
                var rlitem = createElement(document, NS_XUL, "richlistitem", {class: "page loading"});
                var rlbox = createElement(document, NS_XUL, "richlistbox", {class: "filteredlistbox", value: entry.index}, [rlitem]);
                this.$pageDeck.appendChild(rlbox);
                rlbox.clearSelection();
                
                // richlistbox doesn't support unselect
                // -> add unselect support by listening for "click" ("select" is fired first)
                rlbox.addEventListener("select", function(e) { self.onImageSelect(e); }, false);
                rlbox.addEventListener("click", function(e) { self.onImageClick(e); }, false);
            }
            
            this._updateSelectedCount();
            this._updateImageInfo();
            wizard.updateWizardButtons();
        }
    },
    
    observe: function(subject, topic, data)
    {
        switch (topic)
        {
            case "nsPref:changed":
                this.observePreference(subject, topic, data);
                break;
        }
    },
    
    observePreference: function(subject, topic, data)
    {
        var prefNamePrefix = "extensions.thumbsdown.scriptwizard.";
        
        switch (data.substr(prefNamePrefix.length))
        {
            case "minImageWidth":
            case "minImageHeight":
            case "minImageMode":
                // TODO: use non-0 delay
                this._filter = null;
                callLater(0, this, this._updateImageListbox);
                break;
        }
    },
    
    onThumbnailSelect: function()
    {
        this._updateThumbnailInfo();
        this._updateImageListbox(this.autoGuessImage);
    },
    
    onPageLoad: function(loader, entry)
    {
        try { this._onPageLoad(loader.content, entry); }
        finally { loader.destroy(); }
    },
    
    onPageError: function(loader, entry)
    {
        try { this._onPageLoad(null, entry); }
        finally { loader.destroy(); }
    },
    
    _onPageLoad: function(doc, entry)
    {
        let target = entry.target;
        
        if (doc)
        {
            var images = [];
            for (var i = 0; i < doc.images.length; i++)
            {
                // <img> must be cloned because its source document has been destroyed
                let img1 = doc.images[i];
                let img2 = document.importNode(img1);
                // if src is not resolved, relative img urls would use chrome: as baseURI
                if (img1.src)
                    img2.src = Services.io.newURI(img1.src, null, img1.baseURIObject).spec;
                images.push(img2);
             }
            
            target.images = images;
        }
        
        // TODO: handle how?
        if (page != pages.images)
        {
            this.dirty.page = true;
            return;
        }
        
        // find thumbnail's rlbox
        var rlbox = this.$pageDeck.firstChild.nextSibling;
        while (rlbox && (rlbox.getAttribute("value") != entry.index))
            rlbox = rlbox.nextSibling;
        
        this._populateImageListbox(rlbox, target.images);
    },
    
    onImageLoad: function(e)
    {
        var image = e.target;
        var rlitem = image.parentNode;
        var rlbox = rlitem.parentNode;
        var deck = rlbox.parentNode;
        
        if (rlbox != deck.selectedPanel)
            return;
        
        rlbox.applyFilterForItem(rlitem);
        this._updateImageListbox();
    },
    
    onImageSelect: function(e)
    {
        var rlbox = e.target;
        rlbox.selectInProgress = true;
        
        // prevent selecting "page" item
        if (rlbox.selectedItem && (rlbox.selectedItem.className.search("page") != -1))
        {
            rlbox.suppressOnSelect = true;
            rlbox.selectedIndex = -1;
            rlbox.suppressOnSelect = false;
            return;
        }
        
        var deck = rlbox.parentNode;
        let entry = this.entries[deck.selectedIndex - 1];
        entry.target.image = ((rlbox.selectedCount != 0) ? entry.target.images[parseInt(rlbox.selectedItem.value)] : null);
        
        this._updateSelectedCount();
        this._updateImageInfo();
        this._updateImageCount();
        wizard.updateWizardButtons();
    },
    
    onImageClick: function(e)
    {
        let rlitem = e.target;
        while (rlitem && (rlitem.nodeName != "richlistitem"))
            rlitem = rlitem.parentNode;
        
        if (! rlitem)
            return;
        
        let rlbox = rlitem.parentNode;
        
        // selectInProgress is set in onImageSelect(),
        // but select event is *not* fired if the item that was clicked is already selected
        if ((! rlbox.selectInProgress) && (e.button == 0))
            rlbox.selectedIndex = -1;
        
        rlbox.selectInProgress = false;
    },
    
    guessImage: function()
    {
        // TODO: test other rlbox states
        
        let min = {index: -1, value: Number.MAX_VALUE};
        
        let index = this.$thumbnailList.currentIndex;
        let entry = this.entries[index];
        let thumbnailUrl = entry.thumbnail.thumbnailUrl;
        
        let rlbox = this.$imageList;
        for (let i = 0; i < rlbox.childNodes.length; i++)
        {
            let image = rlbox.childNodes[i].firstChild;
            let value = levenshtein(thumbnailUrl, image.src);
            if (value < min.value)
                min = {index: i, value: value};
        }
        
        rlbox.selectedIndex = min.index;
        rlbox.ensureIndexIsVisible(min.index);
    },
    
    toggleAutoGuessImage: function()
    {
        this.autoGuessImage = (! this.autoGuessImage);
        
        if (this.autoGuessImage && (! this.$imageList.selectedItem))
            this.guessImage();
    },
    
    toggleFilterImages: function()
    {
        this.filterImages = (! this.filterImages);
        
        if (! this.$imageList)
            return;
        
        this.$imageList.applyFilter();
        this._updateImageListbox();
    },
    
    onImageListboxContextMenuShowing: function(e)
    {
        let node = this.$imageListMenuPopup.triggerNode;
        while (node && (node.nodeName != "richlistitem"))
            node = node.parentNode;
        
        let onImage = (!! node);
        for each (let elem in NodeIterator(this.$imageListMenuPopup.getElementsByClassName("image")))
            elem.hidden = (! onImage);
        
        this.$showLarger.hidden = this.filterImages;
    },
    
    onHideSmaller: function()
    {
        var rlitem = this.$imageList.selectedItem;
        var image = rlitem.firstChild;
        
        var minWidth = ThumbsDown.prefs.getIntPref("scriptwizard.minImageWidth");
        var minHeight = ThumbsDown.prefs.getIntPref("scriptwizard.minImageHeight");
        var mode = ThumbsDown.prefs.getIntPref("scriptwizard.minImageMode");
        
        var minBox = new Box(minWidth, minHeight);
        var imageBox = new Box(image.naturalWidth, image.naturalHeight);
        minBox.enlarge(imageBox, (mode == FILTER_MODE_EITHER));
        
        ThumbsDown.prefs.setIntPref("scriptwizard.minImageWidth", minBox.width);
        ThumbsDown.prefs.setIntPref("scriptwizard.minImageHeight", minBox.height);
        
        this.$imageList.selectedIndex = -1;
        this.filterImages = true;
        this._updateImageListbox();
    },
    
    onShowLarger: function(e)
    {
        var rlitem = this.$imageList.selectedItem;
        var image = rlitem.firstChild;
        
        var minWidth = ThumbsDown.prefs.getIntPref("scriptwizard.minImageWidth");
        var minHeight = ThumbsDown.prefs.getIntPref("scriptwizard.minImageHeight");
        var mode = ThumbsDown.prefs.getIntPref("scriptwizard.minImageMode");
        
        var minBox = new Box(minWidth, minHeight);
        var imageBox = new Box(Math.max(0, image.naturalWidth - 1), Math.max(0, image.naturalHeight - 1));
        minBox.shrink(imageBox, (mode == FILTER_MODE_BOTH));
        
        ThumbsDown.prefs.setIntPref("scriptwizard.minImageWidth", minBox.width);
        ThumbsDown.prefs.setIntPref("scriptwizard.minImageHeight", minBox.height);
        
        this.filterImages = true;
        this._updateImageListbox();
    },
    
    _populateImageListbox: function(rlbox, images)
    {
        if (images && (images.length))
        {
            var self = this;
            
            // remove "page" item
            rlbox.removeChild(rlbox.firstChild);
            
            for (var i = 0; i < images.length; i++)
            {
                var attrs = {maxwidth: 128, maxheight: 128, src: images[i].src, class: "show-throbber show-error"};
                var scaledimage = createElement(document, NS_XUL, "scaledimage", attrs);
                // load handler is delayed so that scaledimage's own load handler has time to go first
                scaledimage.addEventListener("load", function(e) { callLater(0, self, self.onImageLoad, e); }, false);
                
                var rlitem = createElement(document, NS_XUL, "richlistitem", {class: "filteredlistitem", value: i}, [scaledimage]);
                
                rlbox.setAttribute("context", "popup_images");
                rlbox.appendChild(rlitem);
            }
        }
        else
        {
            var msg = (images ? _("error.pageHasNoImages") : _("error.pageLoadError"));
            var label = createElement(document, NS_XUL, "label", {value: msg});
            rlbox.firstChild.appendChild(label);
            rlbox.firstChild.className = "page";  // remove "loading"
            rlbox.disabled = true;
        }
    },
    
    _updateSelectedCount: function()
    {
        document.getElementById("lbl_images_selectedCount").value = this.selectedCount + "/" + this.entries.length;
    },
    
    _updateThumbnailInfo: function()
    {
        var index = this.$thumbnailList.currentIndex;
        let entry = this.entries[index];
        
        document.getElementById("lbl_images_thumbnail").value = entry.thumbnail.thumbnailUrl;
        document.getElementById("lbl_images_link").value = entry.linkUrl;
    },
    
    _updateImageListbox: function(guessImage)
    {
        var index = this.$thumbnailList.currentIndex;
        this.$pageDeck.selectedIndex = index + 1;  // +1 is the no-thumbnail-selected-yet -panel
        
        this.$imageList.filter = this.filter;
        
        if (guessImage && (! this.$imageList.selectedItem))
            this.guessImage();
        
        this._updateSelectedCount();
        this._updateImageInfo();
        this._updateImageCount();
        wizard.updateWizardButtons();
    },
    
    _updateImageInfo: function()
    {
        var rlitem = (this.$imageList ? this.$imageList.selectedItem : null);
        var scaledimage = (rlitem ? rlitem.firstChild : null);
        var show = (scaledimage && (scaledimage.state == "loaded"));
        
        document.getElementById("lbl_images_image").value = (scaledimage ? scaledimage.src : "");
        if (show)
            document.getElementById("lbl_images_imageSize").value = (scaledimage.naturalWidth + "x" + scaledimage.naturalHeight);
        document.getElementById("lbl_images_imageSize").parentNode.hidden = (! show);
    },
    
    _updateImageCount: function()
    {
        var msg = "";
        
        var rlbox = this.$imageList;
        if (rlbox)
        {
            var selectedCount = rlbox.selectedCount;
            var rowCount = rlbox.filteredItemCount;
            var imageCount = rlbox.itemCount;
            
            var msg = selectedCount + " / " + rowCount;
            if (rowCount != imageCount)
                msg += " (" + imageCount + ")"
        }
        
        document.getElementById("lbl_images_imageCount").value = msg;
    },
    
    canAdvance: function()
    {
        return (this.selectedCount >= 2);
    },
    
    onPageAdvanced: function()
    {
        var images = [];
        
        for each (let entry in this.entries)
            images.push(entry.target.image ? entry.target.image : null);
        
        model.images = images;
        
        return true;
    }
}

extend(ImagesPage, WizardPage);

pages.images = new ImagesPage();
